/*******************************************************************************
 * Copyright (c) 2011 LegSem.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl-3.0.txt
 * 
 * Contributors:
 *     LegSem - initial API and implementation
 ******************************************************************************/
package com.legsem.legstar.jca;

import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Vector;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.legsem.legstar.LegStarUtil;
import com.legsem.legstar.cci.LegStarConnection;
import com.legstar.messaging.HostEndpoint;
import com.legstar.messaging.RequestException;

/**
 * Generic, transport agnostic, managed connection.
 * <p/>
 * This MC does not support connection sharing (Multiple Connection clients
 * using the same MC instance).
 * 
 */
public abstract class LegStarManagedConnectionImpl implements
        LegStarManagedConnection {

    /** Logger. */
    private final Log log = LogFactory.getLog(getClass());

    private static final String TRANSACTIONS_NOT_SUPPORTED_ERROR = "Transactions not supported";
    private static final String INVALID_CONNECTION_ERROR = "Connection is invalid";
    private static final String DESTROYED_ERROR = "ManagedConnection already destroyed";

    private transient PrintWriter writer;

    private LegStarConnection connection;
    private com.legstar.messaging.LegStarConnection hostConnection;
    private HostEndpoint hostEndpoint;
    private Vector < ConnectionEventListener > listeners = new Vector < ConnectionEventListener >();
    private boolean isDestroyed;

    public LegStarManagedConnectionImpl(
            com.legstar.messaging.LegStarConnection hostConnection,
            HostEndpoint hostEndpoint) {
        this.hostConnection = hostConnection;
        this.hostEndpoint = hostEndpoint;

    }

    /**
     * The getConnection() method first invalidates an existing connection
     * handle, if one exists, then creates a new connection handle, storing it
     * in an instance variable. It then returns a reference to that instance
     * variable. This maintains a one-to-one relationship between the connection
     * handle and the ManagedConnection. A resource adapter may have multiple
     * connection handles associated with a ManagedConnection, but is not
     * required to do so.
     */
    public Object getConnection(Subject subject,
            ConnectionRequestInfo connectionRequestInfo)
            throws ResourceException {
        if (log.isDebugEnabled()) {
            log.debug("Getting a new connection for subject: " + LegStarUtil.toString(subject)
                    + ", requestInfo: " + connectionRequestInfo);
        }

        checkDestroyed();

        if (connection != null) {
            connection.invalidate();
        }
        connection = createConnection(this);
        return connection;
    }

    /**
     * Create the actual connection for a specific transport.
     * 
     * @parm managedConnection the managed connection
     * @return the CCI connection
     */
    public abstract LegStarConnection createConnection(
            LegStarManagedConnection managedConnection);

    public void associateConnection(Object newConnection)
            throws ResourceException {
        if (log.isDebugEnabled()) {
            log.debug("Changing managed connection association");
        }

        checkDestroyed();

        connection = (LegStarConnection) newConnection;
        connection.setManagedConnection(this);

    }

    /**
     * The application server can also initiate cleanup of a ManagedConnection
     * instance when the container terminates the application component instance
     * that has the corresponding connection handle. The application server
     * should call ManagedConnection.cleanup to initiate the connection cleanup.
     * After the cleanup, the application server puts the ManagedConnection
     * instance into the pool to serve future allocation requests.
     */
    public void cleanup() throws ResourceException {
        if (log.isDebugEnabled()) {
            log.debug("Cleaning up managed connection");
        }

        checkDestroyed();

        if (connection != null) {
            connection.invalidate();
        }

    }

    /**
     * To manage the size of the connection pool, the application server can
     * call ManagedConnection. destroy method to destroy a ManagedConnection. A
     * ManagedConnection instance handles this method call by closing the
     * physical connection to the EIS instance and releasing all system
     * resources held by this instance. The application server also calls
     * ManagedConnection.destroy when it receives a connection error event
     * notification that signals a fatal error on the physical connection.
     */
    public void destroy() throws ResourceException {
        if (log.isDebugEnabled()) {
            log.debug("Destroying managed connection");
        }
        if (isDestroyed)
            return;

        if (connection != null) {
            connection.invalidate();
            connection = null;
            listeners.clear();

            try {
                hostConnection.close();
            } catch (RequestException e) {
                throw new ResourceException(e);
            }
        }
        isDestroyed = true;

    }

    public ManagedConnectionMetaData getMetaData() throws ResourceException {

        checkDestroyed();

        if (connection == null) {
            throw new ResourceException(INVALID_CONNECTION_ERROR);
        }
        return new LegStarManagedConnectionMetaDataImpl(getConnection()
                .getMetaData());
    }

    public LocalTransaction getLocalTransaction() throws ResourceException {
        if (log.isDebugEnabled()) {
            log.debug("Creating new container-managed local transaction");
        }
        return new LegStarManagedLocalTransactionImpl(this);
    }

    public XAResource getXAResource() throws ResourceException {
        throw new NotSupportedException(TRANSACTIONS_NOT_SUPPORTED_ERROR);
    }

    public void addConnectionEventListener(ConnectionEventListener listener) {
        if (log.isDebugEnabled()) {
            log.debug("Adding a new listener to managed connection");
        }
        listeners.add(listener);

    }

    public void removeConnectionEventListener(ConnectionEventListener listener) {
        if (log.isDebugEnabled()) {
            log.debug("Removing a listener for managed connection");
        }
        listeners.remove(listener);
    }

    /**
     * Notify all listeners that a connection a closed.
     */
    public void close() throws ResourceException {

        if (log.isDebugEnabled()) {
            log.debug("Notifying listeners that managed connection is closing");
        }

        notifyListeners(ConnectionEvent.CONNECTION_CLOSED);
    }

    /**
     * Notify all listeners that something went wrong.
     * <p/>
     * Make sure any uncommitted changes are rolled back.
     * @param exception the exception caught
     */
    public void onError(Exception exception) {

        if (log.isDebugEnabled()) {
            log.debug("Notifying listeners that an error occurred", exception);
        }

        notifyListeners(ConnectionEvent.CONNECTION_ERROR_OCCURRED, exception);
    }

    public void transactionStarted() {
        if (log.isDebugEnabled()) {
            log.debug("Notifying listeners that transaction is started");
        }
        notifyListeners(ConnectionEvent.LOCAL_TRANSACTION_STARTED);

    }

    public void transactionCompleted() {
        if (log.isDebugEnabled()) {
            log.debug("Notifying listeners that transaction is committed");
        }
        notifyListeners(ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);

    }

    public void transactionRolledback() {
        if (log.isDebugEnabled()) {
            log.debug("Notifying listeners that transaction is rolled back");
        }
        notifyListeners(ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);

    }

    /**
     * Notify all listeners that a connection event has occurred.
     * 
     * @param eventId the connection event ID
     */
    protected void notifyListeners(int eventId) {
        notifyListeners(eventId, null);
    }
    
    /**
     * Notify all listeners that a connection event has occurred.
     * 
     * @param eventId the connection event ID
     * @param exception the exception if event is related with an error (null otherwise)
     */
    protected void notifyListeners(int eventId, Exception exception) {
        Enumeration < ConnectionEventListener > list = listeners.elements();
        ConnectionEvent event = new ConnectionEvent(this, eventId, exception);
        event.setConnectionHandle(connection);
        while (list.hasMoreElements()) {
            ConnectionEventListener listener = (ConnectionEventListener) list
                    .nextElement();
            switch (eventId) {
            case ConnectionEvent.CONNECTION_CLOSED:
                listener.connectionClosed(event);
                break;

            case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
                listener.localTransactionStarted(event);
                break;

            case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
                listener.localTransactionCommitted(event);
                break;

            case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
                listener.localTransactionRolledback(event);
                break;

            case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
                listener.connectionErrorOccurred(event);
                break;

            default:
                throw new IllegalArgumentException("Illegal eventType: "
                        + eventId);
            }
        }
    }

    protected void checkDestroyed() throws ResourceException {
        if (isDestroyed) {
            throw new javax.resource.spi.IllegalStateException(DESTROYED_ERROR);
        }
    }

    public PrintWriter getLogWriter() throws ResourceException {
        return writer;
    }

    public void setLogWriter(PrintWriter printWriter) throws ResourceException {
        writer = printWriter;

    }

    public com.legstar.messaging.LegStarConnection getHostConnection() {
        return hostConnection;
    }

    public HostEndpoint getHostEndpoint() {
        return hostEndpoint;
    }

    public LegStarConnection getConnection() {
        return connection;
    }

}
